﻿using System;
using System.Collections.Generic;
using System.Linq;
using Mercurial;
using Mercurial.Extensions.Churn;
using System.Collections;
using Blame.Entity;
using System.Text.RegularExpressions;

namespace Blame.Decorator
{
    public class MercurialDecorator : DVCSDecorator
    {

        Repository _repo = null;
        Dictionary<User, ChurnInfo> users = null;

        public Repository Repo
        {
            get { return _repo; }
            set { _repo = value; }
        }

        /// <summary>
        /// Connects to the repository given a path.
        /// </summary>
        /// <param name="fullPath">The full path to the repository.</param>
        public void ConnectToRepo(string fullPath)
        {
            Repo = new Repository(fullPath);
            users = new Dictionary<User, ChurnInfo>();
        }

        /// <summary>
        /// Returns the list of contributors that have contributed to the repository.
        /// </summary>
        /// <returns>A list of contributors.</returns>
        public IEnumerable<User> GetUsers()
        {
            if (Repo == null)
                throw new ApplicationException();

            var log = Repo.Log(new LogCommand()
                .WithIncludePathActions())
                .Select(lg => new { AuthorName = lg.AuthorName, AuthorEmailAddress = lg.AuthorEmailAddress })
                .Distinct()
                .OrderBy(lg => lg.AuthorName);

            foreach (var c in log)
            {
                users.Add(new User(c.AuthorName, c.AuthorEmailAddress), null);
            }

            return new List<User>(users.Keys);
        }

        /// <summary>
        /// Gets all the logs/changesets associated with a user.
        /// </summary>
        /// <param name="user">The user to retrieve logs/changesets for.</param>
        /// <returns>A list of logs/changesets associated with the user.</returns>
        public List<Changeset> GetUserLog(User user)
        {
            if (Repo == null)
                throw new ApplicationException();

            IEnumerable<Mercurial.Changeset> log = Repo.Log(new LogCommand()
                .WithIncludePathActions()
                .WithUser(user.Name));

            List<Changeset> changesetList = new List<Changeset>();
            foreach (Mercurial.Changeset cs in log.ToList<Mercurial.Changeset>())
            {
                Changeset myCs = new Changeset();
                myCs.AuthorEmailAddress = cs.AuthorEmailAddress;
                myCs.AuthorName = cs.AuthorName;
                myCs.Branch = cs.Branch;
                myCs.CommitMessage = cs.CommitMessage;
                myCs.Hash = cs.Hash;
                List<ChangesetPathAction> myCsPACollection = new List<ChangesetPathAction>();
                foreach (Mercurial.ChangesetPathAction csPA in cs.PathActions)
                {
                    ChangesetPathAction myCsPA = new ChangesetPathAction();
                    switch (csPA.Action)
                    {
                        case ChangesetPathActionType.Add: myCsPA.Action = "Add";
                            break;
                        case ChangesetPathActionType.Modify: myCsPA.Action = "Modify";
                            break;
                        default: myCsPA.Action = "Remove";
                            break;
                    }
                    myCsPA.Path = csPA.Path;
                    myCsPA.Source = csPA.Source;

                    myCsPACollection.Add(myCsPA);
                }
                myCs.PathActions = myCsPACollection;
                myCs.RevisionNumber = cs.RevisionNumber;
                List<string> tagList = new List<string>();
                foreach (string tag in cs.Tags)
                {
                    tagList.Add(tag);
                }
                myCs.Tags = tagList;
                myCs.Timestamp = cs.Timestamp;

                changesetList.Add(myCs);
            }

            return changesetList;
        }

        /// <summary>
        /// Gets a user's churn information based on line count.
        /// </summary>
        /// <param name="user">The user to retrieve churn information for.</param>
        /// <param name="totalAddRemoveCount">The total number of add and remove (line) counts in the project across all users.</param>
        /// <param name="totalAddCount">The total number of lines added to the project across all users.</param>
        /// <param name="totalRemoveCount">The total number of lines removed from the project across all users.</param>
        /// <returns>A ChurnInfo object containing the user's individual line counts.</returns>
        public ChurnInfo GetUserContributionByLines(User user, out int totalAddRemoveCount, out int totalAddCount, out int totalRemoveCount)
        {
            if (Repo == null)
                throw new ApplicationException();

            return GetUserContributionByX(user, out totalAddRemoveCount, out totalAddCount, out totalRemoveCount, ChurnUnit.Lines);
        }

        /// <summary>
        /// Gets a user's churn information based on changeset count.
        /// </summary>
        /// <param name="user">The user to retrieve churn information for.</param>
        /// <param name="totalAddRemoveCount">The total number of add and remove (changeset) counts in the project across all users.</param>
        /// <param name="totalAddCount">The total number of changesets added to the project across all users.</param>
        /// <param name="totalRemoveCount">The total number of changesets removed from the project across all users.</param>
        /// <returns>A ChurnInfo object containing the user's individual changeset counts.</returns>
        public ChurnInfo GetUserContributionByChangesets(User user, out int totalAddRemoveCount, out int totalAddCount, out int totalRemoveCount)
        {
            if (Repo == null)
                throw new ApplicationException();

            return GetUserContributionByX(user, out totalAddRemoveCount, out totalAddCount, out totalRemoveCount, ChurnUnit.Changesets);
        }

        private ChurnInfo GetUserContributionByX(User user, out int totalAddRemoveCount, out int totalAddCount, out int totalRemoveCount, ChurnUnit unit)
        {
            var report = Repo.Churn(new ChurnCommand()
                .WithGroupTemplate("{author|person}")
                .WithUnit(unit));

            totalAddRemoveCount = report.Sum(lg => lg.Total);
            totalAddCount = report.Sum(lg => lg.Additions);
            totalRemoveCount = report.Sum(lg => lg.Removals);

            report = report.Where(c => c.GroupName == user.Name);

            ChurnGroup temp = report.First<ChurnGroup>();

            return new ChurnInfo(temp.Total, temp.Additions, temp.Removals);
        }

        /// <summary>
        /// Gets the information from hg churn by lines.
        /// </summary>
        /// <param name="totalAddRemoveCount">The total number of add and remove (line) counts in the project across all users.</param>
        /// <param name="totalAddCount">The total number of lines added to the project across all users.</param>
        /// <param name="totalRemoveCount">The total number of lines removed from the project across all users.</param>
        /// <returns>A List of ChurnInfo objects all the users' line counts.</returns>
        public Dictionary<User, ChurnInfo> GetChurnStatisticsByLines(out int totalAddRemoveCount, out int totalAddCount, out int totalRemoveCount)
        {
            return GetChurnStatisticsByX(out totalAddRemoveCount, out totalAddCount, out totalRemoveCount, ChurnUnit.Lines);
        }

        /// <summary>
        /// Gets the information from hg churn by changesets.
        /// </summary>
        /// <param name="totalAddRemoveCount">The total number of add and remove (changeset) counts in the project across all users.</param>
        /// <param name="totalAddCount">The total number of changesets added to the project across all users.</param>
        /// <param name="totalRemoveCount">The total number of changesets removed from the project across all users.</param>
        /// <returns>A List of ChurnInfo objects all the users' changeset counts.</returns>
        public Dictionary<User, ChurnInfo> GetChurnStatisticsByChangesets(out int totalAddRemoveCount, out int totalAddCount, out int totalRemoveCount)
        {
            return GetChurnStatisticsByX(out totalAddRemoveCount, out totalAddCount, out totalRemoveCount, ChurnUnit.Changesets);
        }

        private Dictionary<User, ChurnInfo> GetChurnStatisticsByX(out int totalAddRemoveCount, out int totalAddCount, out int totalRemoveCount, ChurnUnit unit)
        {
            var report = Repo.Churn(new ChurnCommand()
                .WithGroupTemplate("{author}")
                .WithUnit(unit))
                .OrderBy(cg => cg.GroupName);

            totalAddRemoveCount = report.Sum(lg => lg.Total);
            totalAddCount = report.Sum(lg => lg.Additions);
            totalRemoveCount = report.Sum(lg => lg.Removals);

            string authorEmail = string.Empty;
            var re = new Regex(@"^(?<name>.*)\s+<(?<email>.+)>", RegexOptions.None);
            Match ma = null;
            User user = null;

            foreach (var c in report)
            {
                ma = re.Match(c.GroupName);
                user = new User(ma.Groups["name"].Value.Trim(), ma.Groups["email"].Value.Trim());

                if (users.ContainsKey(user))
                    users[user] = new ChurnInfo(c.Total, c.Additions, c.Removals);
                else
                    users.Add(user, new ChurnInfo(c.Total, c.Additions, c.Removals));
            }

            return users;
        }

        public string GenerateDiff(int revisionNumber)
        {
            DiffChangeCommand diff = new DiffChangeCommand().WithRevision(RevSpec.Single(revisionNumber));
            Repo.Execute(diff);
            return diff.RawStandardOutput;
        }

    }
}
